﻿// ===============================================================================
// MarkupCache.cs
// EOS Web Framework
// ===============================================================================
// Copyright (c) EOS Development Group. All rights reserved.
// ===============================================================================

namespace EOS.Framework.Web.Mvc
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Xml;
    using EOS.Framework.Tools;

    /// <summary>
    /// Default markup cache implementation. Holds a markup for 30 seconds.
    /// </summary>
    public class MarkupCache : IMarkupCache
    {
        /// <summary>
        /// List of all cached markup documents.
        /// </summary>
        private Dictionary<string, CacheEntry> _cache = new Dictionary<string, CacheEntry>();
        /// <summary>
        /// Specified, how long the files should be stored in the cache!
        /// </summary>
        private int _cacheTime;

        /// <summary>
        /// Represents an item for the cache.
        /// </summary>
        class CacheEntry
        {
            /// <summary>
            /// Time, when the item was created.
            /// </summary>
            public DateTime Created;
            /// <summary>
            /// The xml document.
            /// </summary>
            public XmlDocument Document;

            /// <summary>
            /// Initializes a new instance of the <see cref="CacheEntry"/> class
            /// with the xml document.
            /// </summary>
            /// <param name="document">The xml document.</param>
            public CacheEntry(XmlDocument document)
            {
                Document = document;
                Created = DateTime.Now;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MarkupCache"/> class.
        /// </summary>
        public MarkupCache()
        {
            _cacheTime = MvcConfiguration.Current.CacheTime;
        }

        /// <summary>
        /// Creates a new xml document with the given name by the
        /// specified markup.
        /// </summary>
        /// <param name="markup">The markup, where this document should be created from.</param>
        /// <param name="name">The name of the document for the cache.</param>
        /// <returns>The created xml document.</returns>
        private XmlDocument CreateXmlDocument(string markup, string name)
        {
            bool load = false;

            CacheEntry entry = null;
            lock (_cache)
            {
                if (_cache.TryGetValue(name, out entry) == false)
                {
                    // If the cache doesnt have a markup with the specified name
                    // we load it.
                    load = true;
                }
                else
                {
                    TimeSpan timeDifference = DateTime.Now - entry.Created;
                    if (timeDifference.TotalSeconds >= _cacheTime && markup != null)
                    {
                        // If the cache has a markup with the given name
                        // and this markup is older than 30 seconds, than we have to load it.
                        load = true;
                    }
                }

                if (load)
                {

                        XmlDocument document = new XmlDocument();
                        document.LoadXml(markup);

                        _cache[name] = entry = new CacheEntry(document);
                }
            }
            return entry.Document;
        } 

        #region IMarkupCache Members

        /// <summary>
        /// Clears the cache.
        /// </summary>
        public void Clear()
        {
            _cache.Clear();
        }

        /// <summary>
        /// Create a markup object by the given name.
        /// </summary>
        /// <param name="markup">The markup, where the object will be created from.</param>
        /// <param name="name">The name of the markup, which should be loaded. Cannot be null or empty.</param>
        /// <returns>The loaded markup.</returns>
        /// <exception cref="ArgumentNullException">
        /// 	<para><paramref name="markup"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="name"/> is null (Nothing in Visual Basic).</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// 	<para><paramref name="markup"/> is a string of length 0.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="name"/> is a string of length 0.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="name"/> is no valid xml.</para>
        /// </exception>
        public IMarkup CreateMarkup(string markup, string name)
        {
            if (markup == null)
            {
                throw new ArgumentNullException("markup");
            }

            if (StringHelper.IsEmptyOrBlanksOnly(markup))
            {
                throw new ArgumentException(Properties.Resources.EmptyStringParameter, "markup");
            }

            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (StringHelper.IsEmptyOrBlanksOnly(name))
            {
                throw new ArgumentException(Properties.Resources.EmptyStringParameter, "name");
            }

            XmlDocument xmlDocument = null;
            try
            {
                xmlDocument = CreateXmlDocument(markup, name);
            }
            catch (XmlException e)
            {
                throw new ArgumentException(Properties.Resources.InvalidTemplate, "markup", e);
            }

            return new Markup(xmlDocument);
        }

        /// <summary>
        /// Create a markup object from a markup, which is already loaded and has the given name,
        /// </summary>
        /// <param name="name">The name of the markup, which should be loaded. Cannot be null or empty.</param>
        /// <returns>The loaded markup.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> is null (Nothing in Visual Basic).</exception>
        /// <exception cref="ArgumentException"><paramref name="name"/> is a string of length 0.</exception>
        /// <exception cref="MarkupNotFoundException">A markup with the given name does not exist.</exception>
        public IMarkup CreateMarkup(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (StringHelper.IsEmptyOrBlanksOnly(name))
            {
                throw new ArgumentException(Properties.Resources.EmptyStringParameter, "name");
            }

            if (!_cache.ContainsKey(name))
            {
                throw new MarkupNotFoundException();
            }

            return new Markup(_cache[name].Document);
        }

        /// <summary>
        /// Determines whether there is markup file in the cache with the given name.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns>
        /// 	<c>true</c> if there is markup file in the cache with the given name; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> is null (Nothing in Visual Basic).</exception>
        /// <exception cref="ArgumentException"><paramref name="name"/> is a string of length 0.</exception>
        public bool HasInCache(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (StringHelper.IsEmptyOrBlanksOnly(name))
            {
                throw new ArgumentException(Properties.Resources.EmptyStringParameter, "name");
            }

            bool inCache = true;

            CacheEntry entry = null;
            if (_cache.TryGetValue(name, out entry) == false)
            {
                inCache = false;
            }
            else
            {
                TimeSpan timeDifference = DateTime.Now - entry.Created;
                if (timeDifference.TotalSeconds >= _cacheTime)
                {
                    inCache = false;
                }
            }
            return inCache;
        }

        /// <summary>
        /// Loads the markup from a file.
        /// </summary>
        /// <param name="path">The path of the file.</param>
        /// <param name="name">The name of the markup, which should be loaded. Cannot be null or empty.</param>
        /// <returns>The loaded markup.</returns>
        /// <exception cref="ArgumentNullException">
        /// 	<para><paramref name="path"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="name"/> is null (Nothing in Visual Basic).</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// 	<para><paramref name="path"/> is a string of length 0 or contains only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="name"/> is a string of length 0 or contains only blanks.</para>
        /// </exception>
        public IMarkup LoadMarkupFromFile(string path, string name)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            if (StringHelper.IsEmptyOrBlanksOnly(path))
            {
                throw new ArgumentException(Properties.Resources.EmptyStringParameter, "path");
            }

            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (StringHelper.IsEmptyOrBlanksOnly(name))
            {
                throw new ArgumentException(Properties.Resources.EmptyStringParameter, "name");
            }

            if (!File.Exists(path))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.MarkupNotFound, path), "path");
            }

            DateTime lastChange = File.GetLastWriteTime(path);

            // If the last edit time of the file is newer than the 
            // time, when the file was loaded last, we reload it.
            if (_cache.ContainsKey(path))
            {
                if (lastChange > _cache[path].Created)
                {
                    _cache.Remove(path);
                }
            }

            XmlDocument xmlDocument = null;
            try
            {
                xmlDocument = CreateXmlDocument(File.ReadAllText(path), path);
            }
            catch (XmlException e)
            {
                throw new ArgumentException(Properties.Resources.InvalidTemplate, "markup", e);
            }

            return new Markup(xmlDocument); 
        }

        #endregion
    }
}
